/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.internal.views; import org.eclipse.draw2d.ColorConstants; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Display; import org.xmind.core.internal.dom.NumberUtils; import org.xmind.core.style.IStyle; import org.xmind.core.style.IStyleSheet; import org.xmind.gef.draw2d.geometry.Geometry; import org.xmind.gef.draw2d.geometry.PrecisionPoint; import org.xmind.gef.draw2d.geometry.PrecisionPointPair; import org.xmind.gef.draw2d.geometry.PrecisionRectangle; import org.xmind.gef.draw2d.graphics.GradientPattern; import org.xmind.gef.draw2d.graphics.Path; import org.xmind.ui.mindmap.MindMapUI; import org.xmind.ui.resources.ColorUtils; import org.xmind.ui.style.StyleUtils; import org.xmind.ui.style.Styles; public class StyleFigureUtils { public static final int BOUNDARY_STEP = 16; public static final int BOUNDARY_PADDING = 8; public static final float CALLOUT_RRECT_PARAM = 0.2f; public static final float CALLOUT_ELLIPSE_STARTANGLE = -130; public static final float CALLOUT_ELLIPSE_ARCANGLE = 345; public static final int SPINY_WIDTH = 2; public static final int ROUNDED_CORNER = 7; public static final int ROUNDED_CORNER_ADAPTER = 30; private static final IStyleSheet defaultStyles = MindMapUI .getResourceManager().getDefaultStyleSheet(); public static final IStyle defaultSheetStyle = findOrCreateDefaultStyle( Styles.FAMILY_MAP, IStyle.MAP); public static final IStyle defaultCentralStyle = findOrCreateDefaultStyle( Styles.FAMILY_CENTRAL_TOPIC, IStyle.TOPIC); public static final IStyle defaultMainStyle = findOrCreateDefaultStyle( Styles.FAMILY_MAIN_TOPIC, IStyle.TOPIC); private StyleFigureUtils() { } private static IStyle findOrCreateDefaultStyle(String family, String type) { IStyle style = defaultStyles.findStyle(family); if (style == null) style = defaultStyles.createStyle(type); return style; } public static void angledRel(Path shape, Rectangle relBounds, Point c1, Point c2) { int dx = relBounds.width / 8; int dy = relBounds.height / 8; shape.moveTo(relBounds.getBottomLeft()); c1.setLocation(relBounds.getCenter().translate(-dx, -dy)); shape.lineTo(c1); c2.setLocation(relBounds.getCenter().translate(dx, dy)); shape.lineTo(c2); shape.lineTo(relBounds.getTopRight()); } public static void calloutEllipse(Path shape, Rectangle r) { Rectangle outlineBox = r; shape.addArc(outlineBox.x, outlineBox.y, outlineBox.width, outlineBox.height, CALLOUT_ELLIPSE_STARTANGLE, CALLOUT_ELLIPSE_ARCANGLE); float h = outlineBox.height; shape.lineTo(outlineBox.x, outlineBox.y + h); shape.close(); } public static void calloutRoundRect(Path shape, Rectangle r) { Rectangle box = r; float x = box.x; float y = box.y; float w = box.width; float h = box.height; float dy = h - box.height / 4.0f; float c = getAppliedCorner(r); shape.moveTo(x + w * CALLOUT_RRECT_PARAM, y + dy); shape.lineTo(x + w - c, y + dy); shape.addArc(x + w - c, y + dy - c, c, c, -90, 90); shape.lineTo(x + w, y + c); shape.addArc(x + w - c, y, c, c, 0, 90); shape.lineTo(x + c, y); shape.addArc(x, y, c, c, 90, 90); shape.lineTo(x, y + dy - c); shape.addArc(x, y + dy - c, c, c, 180, 90); shape.lineTo(box.x, box.y + h); shape.close(); } protected static int getAppliedCorner(Rectangle r) { int t = Math.min(r.height, r.width); return ROUNDED_CORNER * t / ROUNDED_CORNER_ADAPTER; } public static void curvedRel(Path shape, Rectangle relBounds, Point c1, Point c2) { int dx = -relBounds.width / 10; int dy = relBounds.height / 10; shape.moveTo(relBounds.getBottomLeft()); Point p1 = relBounds.getTop().translate(-dx, -dy); Point p2 = relBounds.getBottom().translate(dx, dy); shape.cubicTo(p1, p2, relBounds.getTopRight()); c1.setLocation(p1.translate(0, dy)); c2.setLocation(p2.translate(0, -dy)); } public static void diamondTopic(Path shape, Rectangle r) { Rectangle r2 = r; shape.moveTo(r2.getLeft()); shape.lineTo(r2.getBottom()); shape.lineTo(r2.getRight()); shape.lineTo(r2.getTop()); shape.close(); } public static void diamondArrow(Path shape, Point head, double angle, int lineWidth) { int side1 = lineWidth + 3; int side2 = lineWidth + 2; PrecisionPoint p = new PrecisionPoint(head); PrecisionPoint p1 = p.getMoved(angle, side1); PrecisionPoint p2 = p.getMoved(angle - Math.PI / 2, side2); PrecisionPoint p3 = p.getMoved(angle + Math.PI, side1); PrecisionPoint p4 = p.getMoved(angle + Math.PI / 2, side2); shape.moveTo(p1); shape.lineTo(p2); shape.lineTo(p3); shape.lineTo(p4); shape.close(); } public static void dotArrow(Path shape, Point head, double angle, int lineWidth) { PrecisionRectangle bounds = new PrecisionRectangle(head.x, head.y, 0, 0) .expand(lineWidth, lineWidth); shape.addArc(bounds, 0, 360); } public static void drawArrow(Graphics graphics, String arrowValue, Point head, Point tail, int lineWidth) { Path shape = new Path(Display.getCurrent()); boolean fill = true; double angle = new PrecisionPoint(tail).getAngle(new PrecisionPoint( head)); if (Styles.ARROW_SHAPE_DIAMOND.equals(arrowValue)) { diamondArrow(shape, head, angle, lineWidth); } else if (Styles.ARROW_SHAPE_DOT.equals(arrowValue)) { dotArrow(shape, head, angle, lineWidth); } else if (Styles.ARROW_SHAPE_HERRINGBONE.equals(arrowValue)) { herringBone(shape, head, angle, lineWidth); fill = false; } else if (Styles.ARROW_SHAPE_SPEARHEAD.equals(arrowValue)) { spearhead(shape, head, angle, lineWidth); } else if (Styles.ARROW_SHAPE_SQUARE.equals(arrowValue)) { square(shape, head, angle, lineWidth); } else if (Styles.ARROW_SHAPE_TRIANGLE.equals(arrowValue)) { triangle(shape, head, angle, lineWidth); } else { normalArrow(shape, head, angle, lineWidth); fill = false; } if (fill) { graphics.setBackgroundColor(graphics.getForegroundColor()); graphics.fillPath(shape); } graphics.drawPath(shape); shape.dispose(); } public static void drawBoundary(Graphics graphics, Rectangle bounds, IStyle style, IStyle template) { Path shape = new Path(Display.getCurrent()); String shapeValue = getValue(Styles.ShapeClass, style, template); if (shapeValue == null || Styles.BOUNDARY_SHAPE_ROUNDEDRECT.equals(shapeValue)) { roundedRect(shape, bounds); } else if (Styles.BOUNDARY_SHAPE_RECT.equals(shapeValue)) { rectangle(shape, bounds); } else if (Styles.BOUNDARY_SHAPE_SCALLOPS.equals(shapeValue)) { scallops(shape, bounds); } else if (Styles.BOUNDARY_SHAPE_TENSION.equals(shapeValue)) { tension(shape, bounds); } else if (Styles.BOUNDARY_SHAPE_WAVES.equals(shapeValue)) { waves(shape, bounds); } else { roundedRect(shape, bounds); } String fillColorValue = getValue(Styles.FillColor, style, template); if (fillColorValue != null) { Color fillColor = ColorUtils.getColor(fillColorValue); String opacityValue = getValue(Styles.Opacity, style, template); double opacity = NumberUtils.safeParseDouble(opacityValue, 1); int alpha = (int) (opacity * 0xff); graphics.setAlpha(alpha); graphics.setBackgroundColor(fillColor); graphics.fillPath(shape); } Color lineColor = getLineColor(style, template, ColorConstants.gray); String lineWidthValue = getValue(Styles.LineWidth, style, template); int lineWidth = NumberUtils.safeParseInt(lineWidthValue, 3); graphics.setLineWidth(lineWidth); String linePatternValue = getValue(Styles.LinePattern, style, template); int linePattern = StyleUtils.toSWTLineStyle(linePatternValue, SWT.LINE_DASH); graphics.setLineStyle(linePattern); graphics.setAlpha(0xff); graphics.setForegroundColor(lineColor); graphics.drawPath(shape); shape.dispose(); } public static void drawMainBranches(Graphics graphics, Rectangle bounds, boolean spiny, boolean rainbow) { PrecisionPoint center = new PrecisionPoint(bounds.getCenter()); double length = Math.min(bounds.width, bounds.height) / 3; for (int i = 0; i < 6; i++) { double angle = Math.PI * (i - 1) / 3; PrecisionPoint p = center.getMoved(angle, length); if (p.y < center.y) p.y += (center.y - p.y) / 6; else if (p.y > center.y) p.y -= (p.y - center.y) / 6; if (Math.abs(p.y - center.y) > 0.000001) { if (p.x < center.x) p.x -= (center.x - p.x) / 6; else if (p.y > center.x) p.x += (p.x - center.x) / 6; } Color c = rainbow ? ColorUtils.getRainbowColor(i, 6) : ColorConstants.gray; graphics.setAlpha(0xff); graphics.setForegroundColor(c); graphics.setLineWidth(1); graphics.setLineStyle(SWT.LINE_SOLID); if (spiny) { PrecisionPoint c1 = center.getMoved(angle + Math.PI / 3, SPINY_WIDTH); PrecisionPoint c2 = center.getMoved(angle - Math.PI / 3, SPINY_WIDTH); Path shape = new Path(Display.getCurrent()); shape.moveTo(p); shape.lineTo(c1); shape.lineTo(c2); shape.close(); graphics.setBackgroundColor(c); graphics.fillPath(shape); graphics.drawPath(shape); shape.dispose(); } else { graphics.drawLine(center.toDraw2DPoint(), p.toDraw2DPoint()); } graphics.setBackgroundColor(ColorConstants.white); Rectangle oval = new PrecisionRectangle(center, center) .getExpanded(4, 3).toDraw2DRectangle(); graphics.fillOval(oval); } } public static void drawSheetBackground(Graphics graphics, Rectangle bounds, IStyle style, IStyle template) { drawSheetBackground(graphics, bounds, style, template, true); } public static void drawSheetBackground(Graphics graphics, Rectangle bounds, IStyle style, IStyle template, boolean withMainBranches) { Color fillColor = null; String fillColorValue = getValue(Styles.FillColor, style, template); if (fillColorValue != null) fillColor = ColorUtils.getColor(fillColorValue); if (fillColor != null) { graphics.setAlpha(0xff); graphics.setBackgroundColor(fillColor); graphics.fillRectangle(bounds); } // if (fillColor == null) // fillColor = ColorUtils.getColor("#e0e0e0"); //$NON-NLS-1$ // if (withMainBranches) { // String spinyValue = getValue(Styles.SPINY_LINES, style, template); // String rainbowValue = getValue(Styles.RAINBOWCOLOR, style, template); // boolean spiny = Boolean.parseBoolean(spinyValue); // boolean rainbow = Boolean.parseBoolean(rainbowValue); // if (spiny || rainbow) { // drawMainBranches(graphics, bounds, spiny, rainbow); // } // } } public static void drawRelationship(Graphics graphics, Rectangle bounds, IStyle style, IStyle template) { Path shape = new Path(Display.getCurrent()); Point c1 = new Point(); Point c2 = new Point(); String shapeValue = getValue(Styles.ShapeClass, style, template); if (Styles.REL_SHAPE_ANGLED.equals(shapeValue)) { angledRel(shape, bounds, c1, c2); } else if (Styles.REL_SHAPE_STRAIGHT.equals(shapeValue)) { straightRel(shape, bounds, c1, c2); } else { curvedRel(shape, bounds, c1, c2); } Color lineColor = getLineColor(style, template, ColorConstants.gray); String lineWidthValue = getValue(Styles.LineWidth, style, template); int lineWidth = NumberUtils.safeParseInt(lineWidthValue, 3); graphics.setLineWidth(lineWidth); String linePatternValue = getValue(Styles.LinePattern, style, template); int linePattern = StyleUtils.toSWTLineStyle(linePatternValue, SWT.LINE_DOT); graphics.setLineStyle(linePattern); graphics.setAlpha(0xff); graphics.setForegroundColor(lineColor); graphics.drawPath(shape); shape.dispose(); graphics.setLineStyle(SWT.LINE_SOLID); String beginArrowValue = getValue(Styles.ArrowBeginClass, style, template); if (beginArrowValue != null && !Styles.ARROW_SHAPE_NONE.equals(beginArrowValue)) { drawArrow(graphics, beginArrowValue, bounds.getBottomLeft(), c1, lineWidth); } String endArrowValue = getValue(Styles.ArrowEndClass, style, template); if (endArrowValue == null) endArrowValue = Styles.ARROW_SHAPE_NORMAL; if (!Styles.ARROW_SHAPE_NONE.equals(endArrowValue)) { drawArrow(graphics, endArrowValue, bounds.getTopRight(), c2, lineWidth); } } public static Color getBranchConnectionColor(IStyle style, IStyle template, IStyle parentStyle, IStyle parentTemplate, int preferredIndex, Color defaultLineColor) { Color lineColor = null; if (preferredIndex >= 0 && parentStyle != null) { String multiColors = getValue(Styles.MultiLineColors, parentStyle, parentTemplate); if (multiColors == null) multiColors = template.getProperty(Styles.MultiLineColors); if (multiColors != null) { multiColors = multiColors.trim(); String[] colors = multiColors.split("[\\s]+"); //$NON-NLS-1$ if (colors.length > 0) { preferredIndex %= colors.length; String color = colors[preferredIndex].trim(); lineColor = ColorUtils.getColor(color); } } } if (lineColor == null) { lineColor = getLineColor(style, template, defaultLineColor); } return lineColor; } public static Color getLineColor(IStyle style, IStyle template, Color defaultLineColor) { Color lineColor = null; String lineColorValue = getValue(Styles.LineColor, style, template); if (lineColorValue != null) lineColor = ColorUtils.getColor(lineColorValue); if (lineColor == null) { lineColor = defaultLineColor; } return lineColor; } public static String getValue(String key, IStyle style, IStyle template) { String value = style == null ? null : style.getProperty(key); if (value == null) { value = template == null ? null : template.getProperty(key); } return value; } public static void drawLine(Graphics g, Rectangle srcBounds, IStyle srcStyle, IStyle srcTemplate, boolean srcCenterUnderline, Rectangle tgtBounds, IStyle tgtStyle, IStyle tgtTemplate, boolean tgtCenterUnderline, boolean tapered) { String line = getValue(Styles.LineClass, srcStyle, srcTemplate); if (Styles.BRANCH_CONN_NONE.equals(line)) return; String lineWidth = getValue(Styles.LineWidth, srcStyle, srcTemplate); int width = NumberUtils.safeParseInt(lineWidth, 1); srcBounds = srcBounds.getExpanded(-width / 2, -width / 2); int tgtWidth = NumberUtils.safeParseInt(getValue(Styles.LineWidth, tgtStyle, tgtTemplate), 1); tgtBounds = tgtBounds.getExpanded(-tgtWidth / 2, -tgtWidth / 2); String srcShape = getValue(Styles.ShapeClass, srcStyle, srcTemplate); String tgtShape = getValue(Styles.ShapeClass, tgtStyle, tgtTemplate); Point srcPos = getSourcePos(srcBounds, srcShape, tgtBounds, tgtShape, srcCenterUnderline); Point tgtPos = getTargetPos(tgtBounds, tgtShape, srcBounds, srcShape, tgtCenterUnderline); Path shape = new Path(Display.getCurrent()); if (Styles.BRANCH_CONN_ELBOW.equals(line)) { elbow(shape, srcPos, tgtPos, tapered, width); } else if (Styles.BRANCH_CONN_ROUNDEDELBOW.equals(line)) { roundElbow(shape, srcPos, tgtPos, tapered, width); } else if (Styles.BRANCH_CONN_CURVE.equals(line) || Styles.BRANCH_CONN_ARROWED_CURVE.equals(line)) { curveConn(shape, srcPos, tgtPos, tapered, width); } else { // Straight and other unidentifiable line types straightConn(shape, srcPos, tgtPos, tapered, width); } g.setLineWidth(width); g.setLineStyle(SWT.LINE_SOLID); g.setAlpha(0xff); if (tapered) { g.setBackgroundColor(g.getForegroundColor()); g.fillPath(shape); } else g.drawPath(shape); shape.dispose(); } public static Point getSourcePos(Rectangle srcBounds, String srcShape, Rectangle tgtBounds, String tgtShape, boolean centerUnderline) { if (Styles.TOPIC_SHAPE_UNDERLINE.equals(srcShape)) { if (centerUnderline) { if (tgtBounds.getCenter().x < srcBounds.getCenter().x) return srcBounds.getLeft(); return srcBounds.getRight(); } else { if (tgtBounds.getCenter().x < srcBounds.getCenter().x) return srcBounds.getBottomLeft(); return srcBounds.getBottomRight(); } } return Geometry.getChopBoxLocation(tgtBounds.getCenter(), srcBounds); } public static Point getTargetPos(Rectangle tgtBounds, String tgtShape, Rectangle srcBounds, String srcShape, boolean centerUnderline) { if (Styles.TOPIC_SHAPE_UNDERLINE.equals(tgtShape)) { if (centerUnderline) { if (tgtBounds.getCenter().x < srcBounds.getCenter().x) return tgtBounds.getRight(); return tgtBounds.getLeft(); } else { if (tgtBounds.getCenter().x < srcBounds.getCenter().x) return tgtBounds.getBottomRight(); return tgtBounds.getBottomLeft(); } } if (tgtBounds.getCenter().x < srcBounds.getCenter().x) return tgtBounds.getRight(); return tgtBounds.getLeft(); // return Geometry.getChopBoxLocation( srcBounds.getCenter(), tgtBounds ); } public static void drawTopic(Graphics graphics, Rectangle bounds, IStyle style, IStyle template, boolean centerUnderline) { Path shape = new Path(Display.getCurrent()); boolean outline = true; boolean fill = true; String shapeValue = getValue(Styles.ShapeClass, style, template); if (shapeValue == null || Styles.TOPIC_SHAPE_ROUNDEDRECT.equals(shapeValue)) { roundedRect(shape, bounds); } else if (Styles.TOPIC_SHAPE_ELLIPSE.equals(shapeValue)) { ellipse(shape, bounds); } else if (Styles.TOPIC_SHAPE_RECT.equals(shapeValue)) { rectangle(shape, bounds); } else if (Styles.TOPIC_SHAPE_UNDERLINE.equals(shapeValue)) { underline(shape, bounds, centerUnderline); fill = false; } else if (Styles.TOPIC_SHAPE_NO_BORDER.equals(shapeValue)) { noBorder(shape, bounds); outline = false; } else if (Styles.TOPIC_SHAPE_DIAMOND.equals(shapeValue)) { diamondTopic(shape, bounds); } else if (Styles.TOPIC_SHAPE_CALLOUT_ELLIPSE.equals(shapeValue)) { calloutEllipse(shape, bounds); } else if (Styles.TOPIC_SHAPE_CALLOUT_ROUNDEDRECT.equals(shapeValue)) { calloutRoundRect(shape, bounds); } else { roundedRect(shape, bounds); } String fillColorValue = getValue(Styles.FillColor, style, template); graphics.setAlpha(0xff); if (fillColorValue != null && fill) { Color fillColor = ColorUtils.getColor(fillColorValue); if (fillColor != null) { int x = bounds.x; int y1 = bounds.y - bounds.height / 4; int y2 = bounds.y + bounds.height; GradientPattern bgPattern = new GradientPattern(Display .getCurrent(), x, y1, x, y2, ColorConstants.white, 0xff, fillColor, 0xff); graphics.setBackgroundPattern(bgPattern); graphics.fillPath(shape); bgPattern.dispose(); } } if (outline) { Color lineColor = getLineColor(style, template, ColorConstants.gray); String lineWidthValue = getValue(Styles.LineWidth, style, template); int lineWidth = NumberUtils.safeParseInt(lineWidthValue, 1); graphics.setLineWidth(lineWidth); graphics.setLineStyle(SWT.LINE_SOLID); graphics.setForegroundColor(lineColor); graphics.drawPath(shape); } shape.dispose(); } public static void ellipse(Path shape, Rectangle r) { shape.addArc(r, 0, 360); } public static int getBoundaryPadding() { return BOUNDARY_PADDING; } public static void elbow(Path shape, Point p1, Point p2, boolean tapered, int width) { Point c = new Point(p1.x, p2.y); if (tapered) { PrecisionPoint _c = new PrecisionPoint(c); PrecisionPoint _p1 = new PrecisionPoint(p1); PrecisionPoint _p2 = new PrecisionPoint(p2); PrecisionPointPair _cc = Geometry.calculatePositionPair(_c, _p2, 0.5); PrecisionPointPair _pp2 = Geometry.calculatePositionPair(_p2, _c, 0.5).swap(); PrecisionPointPair _pp1 = Geometry.calculatePositionPair(_p1, _c, width); double d = (p1.x > p2.x == p1.y > p2.y) ? width * 0.5 : -width * 0.5; _cc.p1().x -= d; _cc.p2().x += d; shape.moveTo(_pp1.p1()); shape.lineTo(_cc.p1()); shape.lineTo(_pp2.p1()); shape.lineTo(_pp2.p2()); shape.lineTo(_cc.p2()); shape.lineTo(_pp1.p2()); shape.close(); } else { shape.moveTo(p1); shape.lineTo(c); shape.lineTo(p2); } } public static void roundElbow(Path shape, Point p1, Point p2, boolean tapered, int width) { Point c = new Point(p1.x, p2.y); int corner = getAppliedCorner(new Rectangle(p1, p2)) * 2; Point q1 = new Point(c.x, p1.y > p2.y ? c.y + corner : c.y - corner); Point q2 = new Point(p1.x > p2.x ? c.x - corner : c.x + corner, c.y); if (tapered) { PrecisionPoint _p1 = new PrecisionPoint(p1); PrecisionPoint _p2 = new PrecisionPoint(p2); PrecisionPoint _q1 = new PrecisionPoint(q1); PrecisionPoint _q2 = new PrecisionPoint(q2); PrecisionPoint _c1 = new PrecisionPoint(_q1.x, _q1.y + (c.y - _q1.y) * 3 / 4); PrecisionPoint _c2 = new PrecisionPoint(_q2.x + (c.x - _q2.x) * 3 / 4, _q2.y); PrecisionPoint _pc1 = new PrecisionPoint(_p1.x, _p1.y + (_c1.y - _p1.y) * 2); PrecisionPoint _pc2 = new PrecisionPoint(_p2.x + (_c2.x - _p2.x) * 2, _p2.y); PrecisionPointPair _pp1 = Geometry.calculatePositionPair(_p1, _c1, width); PrecisionPointPair _qq1 = Geometry.calculatePositionPair(_q1, _c1, width); PrecisionPointPair _cc1 = Geometry.calculatePositionPair(_c1, _pc1, width); PrecisionPointPair _pp2 = Geometry.calculatePositionPair(_p2, _c2, 0.5).swap(); PrecisionPointPair _qq2 = Geometry.calculatePositionPair(_q2, _c2, 0.5).swap(); PrecisionPointPair _cc2 = Geometry.calculatePositionPair(_c2, _pc2, 0.5).swap(); double d = (p1.x > p2.x == p1.y > p2.y) ? width * 0.5 : -width * 0.5; _qq2.p1().x -= d; _qq2.p2().x += d; _cc2.p1().x -= d; _cc2.p2().x += d; shape.moveTo(_pp1.p1()); shape.lineTo(_qq1.p1()); shape.cubicTo(_cc1.p1(), _cc2.p1(), _qq2.p1()); shape.lineTo(_pp2.p1()); shape.lineTo(_pp2.p2()); shape.lineTo(_qq2.p2()); shape.cubicTo(_cc2.p2(), _cc1.p2(), _qq1.p2()); shape.lineTo(_pp1.p2()); shape.close(); } else { shape.moveTo(p1); shape.lineTo(q1); shape.cubicTo(q1.x, q1.y + (c.y - q1.y) * 3 / 4, q2.x + (c.x - q2.x) * 3 / 4, q2.y, q2.x, q2.y); shape.lineTo(p2); } } public static void straightConn(Path shape, Point p1, Point p2, boolean tapered, int width) { if (tapered) { PrecisionPoint _p1 = new PrecisionPoint(p1); PrecisionPoint _p2 = new PrecisionPoint(p2); PrecisionPointPair _pp1 = Geometry.calculatePositionPair(_p1, _p2, width); PrecisionPointPair _pp2 = Geometry.calculatePositionPair(_p2, _p1, 0.5).swap(); shape.moveTo(_pp1.p1()); shape.lineTo(_pp2.p1()); shape.lineTo(_pp2.p2()); shape.moveTo(_pp1.p2()); shape.close(); } else { shape.moveTo(p1); shape.lineTo(p2); } } public static void curveConn(Path shape, Point p1, Point p2, boolean tapered, int width) { if (tapered) { PrecisionPoint _p1 = new PrecisionPoint(p1); PrecisionPoint _p2 = new PrecisionPoint(p2); PrecisionPoint _c = new PrecisionPoint(_p1.x + (_p2.x - _p1.x) * 2 / 10, _p2.y); PrecisionPointPair _pp1 = Geometry.calculatePositionPair(_p1, _p2, width); PrecisionPointPair _pp2 = Geometry.calculatePositionPair(_p2, _c, 0.5).swap(); PrecisionPointPair _cc = Geometry.calculatePositionPair(_c, _p2, 0.5); double d = (p1.x > p2.x == p1.y > p2.y) ? width * 0.5 : -width * 0.5; _cc.p1().x -= d; _cc.p2().x += d; shape.moveTo(_pp1.p1()); shape.quadTo(_cc.p1(), _pp2.p1()); shape.lineTo(_pp2.p2()); shape.quadTo(_cc.p2(), _pp1.p2()); shape.close(); } else { Point c = new Point(p1.x, p2.y); c.x += (p2.x - c.x) * 2 / 10; shape.moveTo(p1); shape.quadTo(c, p2); } } public static void herringBone(Path shape, Point head, double angle, int lineWidth) { int l = lineWidth * 2 + 4; int w = lineWidth * 2 + 2; PrecisionPoint p = new PrecisionPoint(head); PrecisionPoint p1 = p.getMoved(angle, l / 2); PrecisionPoint p2 = p.getMoved(angle, l); PrecisionPoint p01 = p.getMoved(angle - Math.PI * 2 / 3, w); PrecisionPoint p02 = p.getMoved(angle + Math.PI * 2 / 3, w); PrecisionPoint p11 = p1.getMoved(angle - Math.PI * 2 / 3, w); PrecisionPoint p12 = p1.getMoved(angle + Math.PI * 2 / 3, w); PrecisionPoint p21 = p2.getMoved(angle - Math.PI * 2 / 3, w); PrecisionPoint p22 = p2.getMoved(angle + Math.PI * 2 / 3, w); shape.moveTo(p01); shape.lineTo(head); shape.lineTo(p02); shape.moveTo(p11); shape.lineTo(p1); shape.lineTo(p12); shape.moveTo(p21); shape.lineTo(p2); shape.lineTo(p22); shape.moveTo(head); shape.lineTo(p2); } public static void noBorder(Path shape, Rectangle r) { shape.addRectangle(r); } public static void normalArrow(Path shape, Point head, double angle, int lineWidth) { int side = lineWidth * 2 + 4; PrecisionPoint p = new PrecisionPoint(head); PrecisionPoint p1 = p.getMoved(angle - Math.PI / 6, side); PrecisionPoint p2 = p.getMoved(angle + Math.PI / 6, side); shape.moveTo(p1); shape.lineTo(head); shape.lineTo(p2); } public static void rectangle(Path shape, Rectangle r) { shape.addRectangle(r); } public static void roundedRect(Path shape, Rectangle r) { shape.addRoundedRectangle(r, getAppliedCorner(r)); } public static void scallops(Path shape, Rectangle box) { int margin = getBoundaryPadding() * 3 / 5; if (box.width <= margin * 2 || box.height <= margin * 2) return; float width = box.width - margin * 2; float height = box.height - margin * 2; float stepX = BOUNDARY_STEP; float stepY = BOUNDARY_STEP * 6 / 8; int numX = Math.max(1, (int) (width / stepX)); int numY = Math.max(1, (int) (height / stepY)); stepX = width / numX; stepY = height / numY; float x = box.x + margin; float y = box.y + margin; shape.moveTo(x, y); for (int i = 0; i < numX; i++) { shape.cubicTo(x + stepX / 4, y - margin, x + stepX * 3 / 4, y - margin, x + stepX, y); x += stepX; } for (int i = 0; i < numY; i++) { shape.cubicTo(x + margin, y + stepY / 4, x + margin, y + stepY * 3 / 4, x, y + stepY); y += stepY; } for (int i = 0; i < numX; i++) { shape.cubicTo(x - stepX / 4, y + margin, x - stepX * 3 / 4, y + margin, x - stepX, y); x -= stepX; } for (int i = 0; i < numY; i++) { shape.cubicTo(x - margin, y - stepY / 4, x - margin, y - stepY * 3 / 4, x, y - stepY); y -= stepY; } shape.close(); } public static void spearhead(Path shape, Point head, double angle, int lineWidth) { int side = lineWidth * 2 + 6; PrecisionPoint p = new PrecisionPoint(head); PrecisionPoint p1 = p.getMoved(angle - Math.PI / 8, side); PrecisionPoint p2 = p.getMoved(angle + Math.PI / 8, side); PrecisionPoint cp = p.getMoved(angle, side / 2); shape.moveTo(head); shape.lineTo(p1); shape.quadTo(cp, p2); shape.close(); } public static void square(Path shape, Point head, double angle, int lineWidth) { int side = lineWidth + 2; PrecisionPoint p = new PrecisionPoint(head); PrecisionPoint p1 = p.getMoved(angle - Math.PI / 4, side); PrecisionPoint p2 = p.getMoved(angle - Math.PI * 3 / 4, side); PrecisionPoint p3 = p.getMoved(angle + Math.PI * 3 / 4, side); PrecisionPoint p4 = p.getMoved(angle + Math.PI / 4, side); shape.moveTo(p1); shape.lineTo(p2); shape.lineTo(p3); shape.lineTo(p4); shape.close(); } public static void straightRel(Path shape, Rectangle relBounds, Point c1, Point c2) { Point p = relBounds.getBottomLeft(); shape.moveTo(p); c2.setLocation(p); p = relBounds.getTopRight(); c1.setLocation(p); shape.lineTo(p); } public static void tension(Path shape, Rectangle box) { int margin = getBoundaryPadding() / 2; int margin2 = Math.max(1, margin / 4); if (box.width <= margin * 2 || box.height <= margin * 2) return; float width = box.width - margin2 * 2; float height = box.height - margin2 * 2; float stepX = BOUNDARY_STEP; float stepY = BOUNDARY_STEP; int numX = Math.max(1, (int) (width / stepX)); int numY = Math.max(1, (int) (height / stepY)); stepX = width / numX; stepY = height / numY; float x = box.x + margin2; float y = box.y + margin2; shape.moveTo(x, y); for (int i = 0; i < numX; i++) { shape.cubicTo(x + stepX / 4, y + margin, x + stepX * 3 / 4, y + margin, x + stepX, y); x += stepX; } for (int i = 0; i < numY; i++) { shape.cubicTo(x - margin, y + stepY / 4, x - margin, y + stepY * 3 / 4, x, y + stepY); y += stepY; } for (int i = 0; i < numX; i++) { shape.cubicTo(x - stepX / 4, y - margin, x - stepX * 3 / 4, y - margin, x - stepX, y); x -= stepX; } for (int i = 0; i < numY; i++) { shape.cubicTo(x + margin, y - stepY / 4, x + margin, y - stepY * 3 / 4, x, y - stepY); y -= stepY; } shape.close(); } public static void triangle(Path shape, Point head, double angle, int lineWidth) { int side = lineWidth * 2 + 4; PrecisionPoint p = new PrecisionPoint(head); PrecisionPoint p1 = p.getMoved(angle - Math.PI / 6, side); PrecisionPoint p2 = p.getMoved(angle + Math.PI / 6, side); shape.moveTo(p1); shape.lineTo(head); shape.lineTo(p2); shape.close(); } public static void underline(Path shape, Rectangle r, boolean center) { Rectangle r2 = r; if (center) { shape.moveTo(r2.getLeft()); shape.lineTo(r2.getRight()); } else { shape.moveTo(r2.getBottomLeft()); shape.lineTo(r2.getBottomRight()); } } public static void waves(Path shape, Rectangle box) { int margin = getBoundaryPadding() / 4; if (box.width <= margin * 2 || box.height <= margin * 2) return; float width = box.width - margin * 2; float height = box.height - margin * 2; float stepX = BOUNDARY_STEP; float stepY = BOUNDARY_STEP; int numX = Math.max(1, (int) (width / stepX)); int numY = Math.max(1, (int) (height / stepY)); stepX = width / numX; stepY = height / numY; float x = box.x + margin; float y = box.y + margin; float h = ((float) getBoundaryPadding()) / 4; shape.moveTo(x, y); for (int i = 0; i < numX; i++) { shape.cubicTo(x + stepX / 8, y - h, x + stepX * 3 / 8, y - h, x + stepX / 2, y); shape.cubicTo(x + stepX * 5 / 8, y + h, x + stepX * 7 / 8, y + h, x + stepX, y); x += stepX; } for (int i = 0; i < numY; i++) { shape.cubicTo(x + h, y + stepY / 8, x + h, y + stepY * 3 / 8, x, y + stepY / 2); shape.cubicTo(x - h, y + stepY * 5 / 8, x - h, y + stepY * 7 / 8, x, y + stepY); y += stepY; } for (int i = 0; i < numX; i++) { shape.cubicTo(x - stepX / 8, y + h, x - stepX * 3 / 8, y + h, x - stepX / 2, y); shape.cubicTo(x - stepX * 5 / 8, y - h, x - stepX * 7 / 8, y - h, x - stepX, y); x -= stepX; } for (int i = 0; i < numY; i++) { shape.cubicTo(x - h, y - stepY / 8, x - h, y - stepY * 3 / 8, x, y - stepY / 2); shape.cubicTo(x + h, y - stepY * 5 / 8, x + h, y - stepY * 7 / 8, x, y - stepY); y -= stepY; } shape.close(); } }